/**
 * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
 */
package com.ld.shieldsb.common.core.io;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.ObjectUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.Resource;

import com.ld.shieldsb.common.core.collections.SetUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * Properties工具类， 可载入多个properties、yml文件， 相同的属性在最后载入的文件中的值将会覆盖之前的值， 取不到从System.getProperty()获取。
 * 
 * @author ThinkGem
 * @version 2017-12-30
 */
@Slf4j
public class PropertiesUtils {

    // 默认加载的文件，可通过继承覆盖（若有相同Key，优先加载后面的）,参考https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
    // 打成jar包是，建议将可能修改的配置文件放到jar包外部，通过file寻找
    protected static final List<String> DEFAULT_CONFIG_FILE = new ArrayList<>(Arrays.asList(
            // config文件
            "classpath:config/config.properties", "classpath:config.properties", "file:./config/config.properties",
            "file:./config.properties",
            // 生产环境(建议不要放在本地，与开发环境隔离)
            "file:./product/config/config.properties", "file:./product/config.properties",
            // application文件
            "classpath:config/application.yml", "classpath:application.yml", "classpath:config/application.properties",
            "classpath:application.properties", "file:./config/application.yml", "file:./application.yml",
            "file:./config/application.properties", "file:./application.properties",
            // 生产环境(建议不要放在本地，与开发环境隔离)
            "file:./product/config/application.yml", "file:./product/application.yml", "file:./product/config/application.properties",
            "file:./product/application.properties"));

    /**
     * 注意在获取实例前执行或者增加完后重新获取实例
     * 
     * @Title addConfigFile
     * @author 吕凯
     * @date 2018年7月19日 下午12:11:27
     * @param filePath
     *            void
     */
    public static void addConfigFile(String filePath) {
        List<String> addFilePath = new ArrayList<>();
        if (!filePath.startsWith("classpath:") && !filePath.startsWith("file:")) { // 只写了文件路径，则自动组合
            addFilePath.add("classpath:" + filePath);
            addFilePath.add("file:" + filePath); // file再后面，则优先级比前面的高，如果是jar包则jar包外的比jar包内的优先级高
        } else {
            addFilePath.add(filePath);
        }
        for (String path : addFilePath) {
            if (!DEFAULT_CONFIG_FILE.contains(path)) {
                DEFAULT_CONFIG_FILE.add(path);
            } else {
                log.warn(filePath + " 在配置文件管理器中已经存在！");
            }
        }
    }

    private final Properties properties = new Properties();

    /**
     * 当前类的实例持有者（静态内部类，延迟加载，懒汉式，线程安全的单例模式）
     */
    private static final class PropertiesLoaderHolder {
        private static PropertiesUtils INSTANCE;
        static {
            releadInstance();
        }

        public static void releadInstance() {
            Set<String> configFiles = SetUtils.newLinkedHashSet();
            // classpath：只会到你的class路径中查找找文件;
            // classpath*：不仅包含class路径，还包括jar文件中(class路径)进行查找.
            Resource[] resources = ResourceUtils.getResources("classpath*:/config/shieldsb-*.*");
            for (Resource resource : resources) {
                configFiles.add("classpath:/config/" + resource.getFilename());
            }
            for (String configFile : DEFAULT_CONFIG_FILE) {
                configFiles.add(configFile);
            }
            log.debug("Loading config: {}", configFiles);
            INSTANCE = new PropertiesUtils(configFiles.toArray(new String[configFiles.size()]));
        }
    }

    /**
     * 载入多个文件，路径使用Spring Resource格式，相同的属性在最后载入的文件中的值将会覆盖之前的值。
     */
    public PropertiesUtils(String... configFiles) {
        for (String location : configFiles) {
            try {
                Resource resource = ResourceUtils.getResource(location);
                if (resource.exists()) {
                    String ext = FileUtils.getFileExtension(location);
                    if ("properties".equals(ext)) {
                        try (InputStreamReader is = new InputStreamReader(resource.getInputStream(), "UTF-8");) {
                            properties.load(is);
                        } catch (IOException ex) {
                            log.error("Load " + location + " failure. ", ex);
                        }
                    } else if ("yml".equals(ext) || "yaml".equals(ext)) {
                        YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
                        bean.setResources(resource);
                        for (Map.Entry<Object, Object> entry : bean.getObject().entrySet()) {
                            properties.put(ObjectUtils.toString(entry.getKey()), ObjectUtils.toString(entry.getValue()));
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Load " + location + " failure. ", e);
            }
        }
    }

    /**
     * 获取当前加载的属性
     */
    public Properties getProperties() {
        return properties;
    }

    /**
     * 当前类实例
     */
    public static PropertiesUtils getInstance() {
        return PropertiesLoaderHolder.INSTANCE;
    }

    /**
     * 重新加载实例（重新实例化，以重新加载属性文件数据）
     */
    public static void releadInstance() {
        PropertiesLoaderHolder.releadInstance();
    }

    // 正则表达式预编译
    private static Pattern p1 = Pattern.compile("\\$\\{.*?\\}");

    /**
     * 获取属性值，取不到从System.getProperty()获取，都取不到返回null
     */
    public String getProperty(String key) {
        return getProperty(key, null);
    }

    /**
     * 取出String类型的Property，但以System的Property优先，如果都为null则返回defaultValue值
     */
    public String getProperty(String key, String defaultValue) {
        String value = properties.getProperty(key);
        if (value != null) {
            // 支持嵌套取值的问题 key=${xx}/yy
            Matcher m = p1.matcher(value);
            while (m.find()) {
                String g = m.group();
                String keyChild = g.replaceAll("\\$\\{", "").replaceAll("\\}", "").trim();
                value = value.replace(g, getProperty(keyChild));
            }
            return value;
        } else {
            String systemProperty = System.getProperty(key);
            if (systemProperty != null) {
                return systemProperty;
            }
        }
        return defaultValue;
    }

}
